"""Task Manager - queue management, concurrency control, status monitoring."""

import asyncio
from collections.abc import Callable
from datetime import datetime
from uuid import UUID

from loguru import logger
from sqlalchemy import select, update

from .downloader import DownloadEngine
from ..models.database import db
from ..models.task import DownloadTask, GlobalConfig, TaskStatus

TaskEventCallback = Callable[[UUID, TaskStatus], None]


class TaskManager:
    """Manages download tasks, including queuing, concurrency, and status monitoring.

    Attributes:
        engine (DownloadEngine): The download engine used to execute tasks.
        _running (bool): Flag indicating if the task manager is running.
        _worker_task (asyncio.Task | None): The background task for the worker loop.
        _semaphore (asyncio.Semaphore | None): Semaphore to control concurrent downloads.
        _event_callbacks (list[TaskEventCallback]): List of callbacks for task events.
        _pending_queue (asyncio.Queue[UUID]): Queue for pending task IDs.
        _active_tasks (dict[UUID, asyncio.Task]): Dictionary of currently active download tasks.
        _pending_status_updates (set[asyncio.Future]): Set of pending status update coroutines.
    """

    def __init__(self) -> None:
        """Initializes the TaskManager.
        """
        self.engine = DownloadEngine(progress_callback=self._on_progress)
        self._running = False
        self._worker_task: asyncio.Task | None = None
        self._semaphore: asyncio.Semaphore | None = None
        self._event_callbacks: list[TaskEventCallback] = []

        # 任务队列(按优先级排序)
        self._pending_queue: asyncio.Queue[UUID] = asyncio.Queue()
        self._active_tasks: dict[UUID, asyncio.Task] = {}
        self._loop = asyncio.get_event_loop()
        self._pending_status_updates = set()

    async def start(self) -> None:
        """Starts the task manager, loading configurations and resuming pending tasks.
        """
        if self._running:
            logger.warning("任务管理器已在运行")
            return

        logger.info("启动任务管理器...")
        self._running = True

        # 加载全局配置
        config = await self._load_config()
        self._semaphore = asyncio.Semaphore(config.max_concurrent_downloads)

        # 清除所有未完成的任务
        await self._clear_all_tasks()

        # 恢复未完成的任务
        await self._resume_pending_tasks()

        # 启动工作线程
        self._worker_task = asyncio.create_task(self._worker_loop())

        logger.info("任务管理器已启动")

    async def stop(self) -> None:
        """Stops the task manager, canceling all active downloads and shutting down the worker.
        """
        if not self._running:
            return

        logger.info("停止任务管理器...")
        self._running = False

        # 取消所有活动任务
        for task_id, task_coro in list(self._active_tasks.items()):
            logger.info(f"取消活动任务: {task_id}")
            await self.engine.cancel_task(task_id)
            task_coro.cancel()

        # 等待工作线程结束
        if self._worker_task:
            self._worker_task.cancel()
            try:
                await self._worker_task
            except asyncio.CancelledError:
                pass

        # 等待所有pending的状态更新完成
        if self._pending_status_updates:
            logger.info(f"等待 {len(self._pending_status_updates)} 个pending状态更新完成")
            for future in list(self._pending_status_updates):
                try:
                    future.result(timeout=5.0)
                except (asyncio.TimeoutError, Exception) as e:
                    logger.warning(f"状态更新超时/失败: {e}")

        logger.info("任务管理器已停止")

    async def add_task(self, task: DownloadTask) -> str:
        """Adds a new download task to the manager.

        Args:
            task (DownloadTask): The download task object to add.

        Returns:
            str: The ID of the newly added task.
        """
        async with db.session() as session:
            # 保存到数据库
            session.add(task)
            await session.commit()

            task_id = task.id
            logger.info(f"添加新任务: {task.title} (ID: {task_id})")

            # 根据全局配置决定是否自动开始
            config = await self._load_config()
            if config.auto_start_downloads:
                await self._enqueue_task(task_id)

            # 触发事件
            self._emit_event(task_id, TaskStatus.PENDING)

            return task_id

    async def pause_task(self, task_id: str) -> bool:
        """Pauses a currently downloading task.

        Args:
            task_id (str): The ID of the task to pause.

        Returns:
            bool: True if the task was successfully paused, False otherwise.
        """
        async with db.session() as session:
            result = await session.execute(select(DownloadTask).where(DownloadTask.id == task_id))
            task = result.scalar_one_or_none()

            if not task:
                logger.error(f"任务不存在: {task_id}")
                return False

            if task.status != TaskStatus.DOWNLOADING:
                logger.warning(f"任务当前不在下载中: {task_id}")
                return False

            # 取消下载
            await self.engine.cancel_task(task_id)

            # 更新状态
            task.status = TaskStatus.PAUSED
            task.updated_at = datetime.utcnow()
            await session.commit()

            logger.info(f"任务已暂停: {task.title}")
            self._emit_event(task_id, TaskStatus.PAUSED)

            return True

    async def resume_task(self, task_id: str) -> bool:
        """Resumes a paused or failed task.

        Args:
            task_id (str): The ID of the task to resume.

        Returns:
            bool: True if the task was successfully resumed, False otherwise.
        """
        async with db.session() as session:
            result = await session.execute(select(DownloadTask).where(DownloadTask.id == task_id))
            task = result.scalar_one_or_none()

            if not task:
                logger.error(f"任务不存在: {task_id}")
                return False

            if task.status not in (TaskStatus.PAUSED, TaskStatus.FAILED):
                logger.warning(f"任务状态不支持恢复: {task.status}")
                return False

            # 重置错误信息
            task.error_message = None
            task.status = TaskStatus.PENDING
            task.updated_at = datetime.utcnow()
            await session.commit()

            # 重新入队
            await self._enqueue_task(task_id)

            logger.info(f"任务已恢复: {task.title}")
            self._emit_event(task_id, TaskStatus.PENDING)

            return True

    async def delete_task(self, task_id: str, delete_file: bool = False) -> bool:
        """Deletes a task and optionally its associated downloaded file.

        Args:
            task_id (str): The ID of the task to delete.
            delete_file (bool): Whether to delete the downloaded file as well. Defaults to False.

        Returns:
            bool: True if the task was successfully deleted, False otherwise.
        """
        async with db.session() as session:
            result = await session.execute(select(DownloadTask).where(DownloadTask.id == task_id))
            task = result.scalar_one_or_none()

            if not task:
                logger.error(f"任务不存在: {task_id}")
                return False

            # 如果正在下载,先取消
            if task.status == TaskStatus.DOWNLOADING:
                await self.engine.cancel_task(task_id)

            # 删除文件
            if delete_file and task.save_path:
                try:
                    from pathlib import Path

                    Path(task.save_path).unlink(missing_ok=True)
                    logger.info(f"已删除文件: {task.save_path}")
                except Exception as e:
                    logger.error(f"删除文件失败: {e}")

            # 从数据库删除
            await session.delete(task)
            await session.commit()

            logger.info(f"任务已删除: {task.title}")
            self._emit_event(task_id, TaskStatus.CANCELLED)

            return True

    async def get_task(self, task_id: str) -> DownloadTask | None:
        """Retrieves a single download task by its ID.

        Args:
            task_id (str): The ID of the task to retrieve.

        Returns:
            DownloadTask | None: The DownloadTask object if found, otherwise None.
        """
        async with db.session() as session:
            result = await session.execute(select(DownloadTask).where(DownloadTask.id == task_id))
            return result.scalar_one_or_none()

    async def list_tasks(
            self, status: TaskStatus | None = None, limit: int = 100
    ) -> list[DownloadTask]:
        """Lists download tasks, optionally filtered by status.

        Args:
            status (TaskStatus | None): Optional status to filter tasks by. Defaults to None.
            limit (int): Maximum number of tasks to return. Defaults to 100.

        Returns:
            list[DownloadTask]: A list of DownloadTask objects.
        """
        async with db.session() as session:
            query = select(DownloadTask)

            if status:
                query = query.where(DownloadTask.status == status)

            query = query.order_by(
                DownloadTask.priority.desc(),  # 优先级高的在前
                DownloadTask.created_at.desc(),  # 创建时间新的在前
            ).limit(limit)

            result = await session.execute(query)
            return list(result.scalars().all())

    def register_event_callback(self, callback: TaskEventCallback) -> None:
        """Registers a callback function to receive task events.

        Args:
            callback (TaskEventCallback): The callback function, which takes task_id (UUID) and status (TaskStatus) as arguments.
        """
        self._event_callbacks.append(callback)

    def _emit_event(self, task_id: UUID, status: TaskStatus) -> None:
        """Emits a task event to all registered callbacks.

        Args:
            task_id (UUID): The ID of the task.
            status (TaskStatus): The new status of the task.
        """
        for callback in self._event_callbacks:
            try:
                callback(task_id, status)
            except Exception as e:
                logger.error(f"事件回调异常: {e}")

    async def _enqueue_task(self, task_id: UUID) -> None:
        """Adds a task ID to the pending queue.

        Args:
            task_id (UUID): The ID of the task to enqueue.
        """
        await self._pending_queue.put(task_id)
        logger.debug(f"任务已入队: {task_id}")

    async def _worker_loop(self) -> None:
        """Main worker loop that continuously fetches and executes tasks from the pending queue.
        """
        logger.info("工作循环已启动")

        while self._running:
            try:
                # 从队列获取任务(带超时,避免阻塞shutdown)
                try:
                    task_id = await asyncio.wait_for(self._pending_queue.get(), timeout=1.0)
                except asyncio.TimeoutError:
                    continue

                # 获取信号量(控制并发数)
                if self._semaphore:
                    await self._semaphore.acquire()

                # 启动下载任务
                download_task = asyncio.create_task(self._execute_task(task_id))
                self._active_tasks[task_id] = download_task

            except asyncio.CancelledError:
                logger.info("工作循环被取消")
                break
            except Exception as e:
                logger.error(f"工作循环异常: {e}")

        logger.info("工作循环已结束")

    async def _execute_task(self, task_id: UUID) -> None:
        """Executes a single download task.

        Args:
            task_id (UUID): The ID of the task to execute.
        """
        try:
            # 加载任务
            task = await self.get_task(task_id)
            if not task:
                logger.error(f"任务不存在: {task_id}")
                return

            # 更新状态为下载中
            async with db.session() as session:
                task.status = TaskStatus.DOWNLOADING
                task.started_at = datetime.utcnow()
                task.updated_at = datetime.utcnow()
                session.add(task)
                await session.commit()

            self._emit_event(task_id, TaskStatus.DOWNLOADING)
            logger.info(f"开始执行任务: {task.title}")

            # 执行下载
            success = await self.engine.download(task)
            logger.debug(f"任务下载结果: {task_id} - success={success} (类型: {type(success)})")

            logger.info(f"任务执行完成: {task.title}")

            # 根据结果更新最终状态，避免卡在 DOWNLOADING
            async with db.session() as session:
                result = await session.execute(select(DownloadTask).where(DownloadTask.id == task_id))
                latest = result.scalar_one_or_none()
                if latest:
                    latest.updated_at = datetime.utcnow()
                    if success:
                        latest.status = TaskStatus.COMPLETED
                        latest.completed_at = datetime.utcnow()
                        latest.progress = 100.0
                        await session.commit()
                        self._emit_event(task_id, TaskStatus.COMPLETED)
                    else:
                        # 若进度钩子已标记失败则保持，否则明确置为失败
                        if latest.status not in (TaskStatus.FAILED, TaskStatus.CANCELLED):
                            latest.status = TaskStatus.FAILED
                            await session.commit()
                            self._emit_event(task_id, TaskStatus.FAILED)

        except Exception as e:
            logger.error(f"执行任务异常: {task_id} - 异常类型: {type(e).__name__} - 详情: {str(e)}", exc_info=True)

            # 标记为失败
            async with db.session() as session:
                result = await session.execute(
                    select(DownloadTask).where(DownloadTask.id == task_id)
                )
                task = result.scalar_one_or_none()
                if task:
                    task.status = TaskStatus.FAILED
                    task.error_message = str(e)
                    task.updated_at = datetime.utcnow()
                    await session.commit()

            self._emit_event(task_id, TaskStatus.FAILED)

        finally:
            # 释放信号量和清理
            if self._semaphore:
                self._semaphore.release()
            self._active_tasks.pop(task_id, None)

    async def _resume_pending_tasks(self) -> None:
        """Resumes any pending or currently downloading tasks from the database upon startup.
        """
        async with db.session() as session:
            # 查找所有待处理和下载中的任务
            result = await session.execute(
                select(DownloadTask).where(
                    DownloadTask.status.in_([TaskStatus.PENDING, TaskStatus.DOWNLOADING])
                )
            )
            tasks = result.scalars().all()

            if not tasks:
                return

            logger.info(f"恢复 {len(tasks)} 个未完成任务")

            for task in tasks:
                # 重置下载中的任务为失败
                if task.status == TaskStatus.DOWNLOADING:
                    task.status = TaskStatus.FAILED
                    task.error_message = "Previous download interrupted"
                    task.updated_at = datetime.utcnow()
                    await session.commit()

                # 重新入队
                await self._enqueue_task(task.id)

    async def _on_progress(self, task_id: UUID, status: TaskStatus, progress: float | None, speed: float | None,
                           eta: str | None, error_message: str | None, current_file_size: str | None = None) -> None:
        """Callback function for download progress updates.

        Args:
            task_id (UUID): The ID of the task being updated.
            status (TaskStatus): The current status of the task.
            progress (float | None): The current progress percentage (0.0 to 100.0).
            speed (float | None): The current download speed in bytes per second.
            eta (str | None): The estimated time of arrival (e.g., '00:05:30').
            error_message (str | None): The error message if the task failed.
            current_file_size (str | None): The current file size.
        """
        # 异步更新数据库(不阻塞下载)
        await self._update_task_status(task_id, status, progress, speed, error_message)

    async def _update_task_status(self, task_id: UUID, status: TaskStatus, progress: float | None, speed: float | None,
                                  error_message: str | None) -> None:
        """Asynchronously updates the progress and status of a task in the database.

        Args:
            task_id (UUID): The ID of the task to update.
            status (TaskStatus): The current status of the task.
            progress (float | None): The current progress percentage.
            speed (float | None): The current download speed.
            error_message (str | None): The error message if the task failed.
        """
        try:
            async with db.session() as session:
                update_values = {"updated_at": datetime.utcnow()}
                if status == TaskStatus.DOWNLOADING:
                    update_values["status"] = TaskStatus.DOWNLOADING
                    if progress is not None: update_values["progress"] = progress
                    if speed is not None: update_values["speed"] = speed
                elif status == TaskStatus.COMPLETED:
                    update_values["status"] = TaskStatus.COMPLETED
                    update_values["completed_at"] = datetime.utcnow()
                    update_values["progress"] = 100.0
                elif status == TaskStatus.FAILED:
                    update_values["status"] = TaskStatus.FAILED
                    update_values["error_message"] = error_message
                    update_values["retry_count"] = DownloadTask.retry_count + 1

                await session.execute(
                    update(DownloadTask)
                    .where(DownloadTask.id == task_id)
                    .values(**update_values)
                )
                await session.commit()
                self._emit_event(task_id, status)
        except Exception as e:
            logger.error(f"更新任务状态失败: {e}")

    async def _load_config(self) -> GlobalConfig:
        """Loads the global configuration from the database.

        If no configuration exists, a default one is created and returned.

        Returns:
            GlobalConfig: The loaded or newly created global configuration.
        """
        async with db.session() as session:
            result = await session.execute(select(GlobalConfig).where(GlobalConfig.id == 1))
            config = result.scalar_one_or_none()

            # 如果没有配置，创建默认配置
            if config is None:
                config = GlobalConfig(id=1)
                session.add(config)
                await session.commit()
                await session.refresh(config)
                logger.info("已创建默认全局配置")

            return config

    async def _clear_all_tasks(self) -> None:
        """Clears all pending and downloading tasks from the database.
        """
        async with db.session() as session:
            await session.execute(
                update(DownloadTask)
                .where(DownloadTask.status.in_([TaskStatus.PENDING, TaskStatus.DOWNLOADING, TaskStatus.FAILED]))
                .values(status=TaskStatus.CANCELLED, error_message="Task cleared by system")
            )
            await session.commit()
            logger.info("所有未完成任务已清除")
